package persistent.prestige.console.lock;

import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.FastThreadLocal;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 主要设计目的：
 *     对同一个key(锁粒度)提供在 指定定时间内的串行化处理，超过锁超时时间外，允许并发执行
 * @author dingwei
 *
 */
public class LocalLock {
	
	private static ConcurrentMap<String /** lock key */, ReentrantLock /** lockObject*/> lockTable = 
			new ConcurrentHashMap<String, ReentrantLock>(1024);
	
	// 这里主要是为了，，如果锁时间过长，超过了锁过期时间，会从lockTable中移除，避免锁无法正常释放
	private static FastThreadLocal<Map<String, ReentrantLock>> threadLocks = new FastThreadLocal<Map<String, ReentrantLock>>() {

		@Override
		protected Map<String, ReentrantLock> initialValue() throws Exception {
			// TODO Auto-generated method stub
			return new HashMap<String, ReentrantLock>();
		}
		
	};
	
	// 目前先代表 5分钟
	private static HashedWheelTimer timer = new HashedWheelTimer(5, TimeUnit.MINUTES);
	
	/**
	 *  
	 * @param lockKey              锁粒度key
	 * @param lockExpireMills      锁对象过期时间，在该时间内不允许相同的lockKey并发,单位毫秒
	 */
	public static void lock(String lockKey, long lockExpireMills) {
		ReentrantLock lock = getLocker(lockKey, lockExpireMills);
		lock.lock();
		threadLocks.get().put(lockKey, lock);
		
	}
	
	/**
	 * 这里不能从lockTable中获取，因为有可能ReentrantLock对象超时，会被移除
	 * @param lockKey
	 */
	public static void unlock(String lockKey) {
		Map<String, ReentrantLock> locks = threadLocks.get();
		try {
			if(locks != null ) {
				ReentrantLock lock = locks.remove(lockKey);
				if(lock != null ) {
					lock.unlock();
				}
			}
			
		} finally {
			if(locks.isEmpty()) {
				threadLocks.remove();
			}
			
		}
	}
	
	
	private static ReentrantLock getLocker(final String lockKey, final long lockExpireMills) {
		//InnerLock lockTable.putIfAbsent(key, value)
		ReentrantLock pre = lockTable.get(lockKey);
		if(pre != null ) {
			return pre;
		}
		
		ReentrantLock lock = new ReentrantLock();
		pre = lockTable.putIfAbsent(lockKey, lock);
		
		if(pre != null ) {
			return pre;
		} else {
			if(lockExpireMills > 0 ) {
				timer.newTimeout(new TimerTask() {
					
					@Override
					public void run(Timeout timeout) throws Exception {
						//System.out.println("time out task execute...");
						lockTable.remove(lockKey);
						
					}
				}, lockExpireMills, TimeUnit.MILLISECONDS);
			}
			
			return lock;
		}
		
	}
	
	

}
